#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/cache.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include <linux/of_spi.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/types.h>

#include "spiflash.h"

/*
 * BIT0 CLK
 * BIT1 CS
 * BIT2 CMD
 * BIT3 DAT
 *
 * 0x0  OUT REG
 * 0x4  EN  REG
 * 0x8  IN  REG
 */
enum {
	SEL =0,
	CLK =1,
	CMD =2,
	DAT =3,

	SEL_EN =SEL,
	CLK_EN =CLK,
	CMD_EN =CMD,
	DAT_EN =DAT,
};
static int debug = 0;

static int bit_test(u32 val, int bit)
{
	return (val & (1<<bit)) != 0;
}

static volatile u32 *virt, *virt_out, *virt_en, *virt_in;

static void spi_enable(void)
{
	*virt_en &= ~(1<<SEL_EN);
	*virt_en &= ~(1<<CLK_EN);
	*virt_en &= ~(1<<CMD_EN);
	*virt_en |= (1<<DAT_EN);

	*virt &= ~(1<<CLK);
	*virt |=  (1<<SEL);
}

static void spi_disable(void)
{
	*virt &= ~(1<<SEL);
	*virt_en |= (1<<SEL_EN);
	*virt_en |= (1<<CMD_EN);
	*virt_en |= (1<<DAT_EN);
	*virt_en |= (1<<CLK_EN);
}

static void spi_sel(void)
{
	*virt &= ~(1<<SEL);
}
static void spi_unsel(void)
{
	*virt |= (1<<SEL);
}

static void spi_clearSCK(void)
{
	*virt &= ~(1<<CLK);
}

static void spi_setSCK(void)
{
	*virt |= (1<<CLK);
}

static void spi_bitMOSI(int b)
{
	if (b)
		*virt |= (1<<CMD);
	else
		*virt &= ~(1<<CMD);
}

static void spi_setMOSI(void)
{
	spi_bitMOSI(1);
}

/* read the bit from DO */
static unsigned char spi_getMISO(void)
{
	u32 val = *virt_in;
	return bit_test(val, DAT);
}

static void spi_reset(void)
{
	spi_clearSCK();
	spi_setMOSI();

	spi_unsel();
	spi_unsel();
	spi_sel();
}

static void spi_sendDataBit(int b)
{
	spi_clearSCK();
	spi_bitMOSI(b);
	spi_setSCK();
	spi_clearSCK();
}

static void spi_outb(unsigned char t)
{
	int i;

	spi_clearSCK();
	for (i = 7; i >= 0; i --)
		spi_sendDataBit(t & (1<<i));
	spi_setMOSI();
}

static unsigned char spi_RecDataBit(void)
{
	unsigned char b;

	spi_clearSCK();
	spi_setSCK();
	b = spi_getMISO();
	spi_clearSCK();

	return b;
}

static unsigned char spi_inb(void)
{
	int i;
	unsigned char t = 0;

	spi_clearSCK();
	for (i = 7; i >= 0; i--) {
		if (spi_RecDataBit())
			t |= (1<<i);
	}
	spi_setMOSI();

	return t;
}

static void spi_ready(int wait)
{
	unsigned char status;
	int cnt = 0;

	do {
		spi_sel();
		spi_outb(SPIFLASH_CMD_RDSR);
		status = spi_inb();
		spi_unsel();
		printk("ready %x, %04d\r", status, cnt);
		udelay(1);
		cnt ++;
	} while ((status & SPIFLASH_STATUS_BUSY) && wait);

}

static int spi_readID(void)
{
    u32 val;

    spi_sel();
    /* read chip id */
    spi_outb(SPIFLASH_CMD_RDID);
    val = spi_inb();
    spi_unsel();

    printk("ID is %x\n", val);

    return val;
}

static int spi_read_page(unsigned long addr, unsigned long total,
		int (*read_cb)(void *priv, unsigned char byte), void *priv)
{
	unsigned char byte;
	unsigned long len = total;

	spi_sel();

	spi_outb(SPIFLASH_CMD_READ);
	spi_outb(addr>>16);
	spi_outb(addr>>8);
	spi_outb(addr);

	while (len) {
		byte = spi_inb();
		if (debug)
			printk("addr:%lx: %02x\n", addr, byte);
		read_cb(priv, byte);
		len--;
		addr ++;
		printk("address: %08lx/%08lx\r", addr, total);
	}
	printk("done\n");

	spi_unsel();

	return 0;
}

static void spi_read_byte(int addr, unsigned char *value)
{
	unsigned char byte;

	spi_sel();
	spi_outb(SPIFLASH_CMD_READ);
	spi_outb(addr>>16);
	spi_outb(addr>>8);
	spi_outb(addr);

	byte = spi_inb();
	*value = byte;

	spi_unsel();
}

static int
spi_write_page(unsigned long addr, unsigned long nbytes, unsigned char *data)
{
	unsigned long total = 0;
	unsigned int page;
	unsigned int pagelen;

	for (page = 0; page < ((nbytes+SPIFLASH_PAGESIZE-1)>>8); page ++) {

		/* enable write */
		spi_sel();
		spi_outb(SPIFLASH_CMD_WREN);
		spi_unsel();

		/* begin write */
		spi_sel();
		spi_outb(SPIFLASH_CMD_PAGEPROG);
		spi_outb(addr>>16);
		spi_outb(addr>>8);
		spi_outb(addr);

		if(((page<<8)+SPIFLASH_PAGESIZE) <= nbytes)
			pagelen = SPIFLASH_PAGESIZE;
		else
			pagelen = nbytes-(page<<8);
		addr += pagelen;

		while (pagelen) {
			spi_outb(*data);
			data ++;
			pagelen --;
			total ++;
		}
		spi_unsel();

		/* wait until write is done */
		printk("\t\t%07d/%07d\r", (int)total, (int)nbytes);
		spi_ready(1);
	}
	return 0;
}

static void spi_erase(void)
{
    printk("Erase .....\n");
                                                                                           
    spi_sel();                                                                             
    spi_outb(SPIFLASH_CMD_WREN); /* write enable */                                                     
    spi_unsel();                                                                           
                                                                                           
    spi_sel();                                                                             
    spi_outb(SPIFLASH_CMD_CHIPERASE); /* chip erase */                                                            
    spi_unsel();                                                                           
                                                                                           
    spi_ready(1);                                                                          
    printk("Erase ... done\n");                                                   
} 

static void spi_sector_erase(unsigned int addr)                                             
{                                                                                          
    spi_sel();                                                                             
    spi_outb(SPIFLASH_CMD_WREN); /* write enable */                                        
    spi_unsel();                                                                           
                                                                                           
    spi_sel();                                                                             
    spi_outb(SPIFLASH_CMD_SECTERASE); /* sector erase */                                   
    spi_outb(addr>>16);                                                                    
    spi_outb(addr>>8);                                                                     
    spi_outb(addr);                                                                        
    spi_unsel();                                                                           
                                                                                           
    spi_ready(1);                                                                          
    //printk("Erase sector... done\n");                                         
}

static int
spi_verify(unsigned long addr, unsigned long nbytes, unsigned char *data)
{
	int err = 0;
	unsigned long len = nbytes;

	/* verify the data */
	spi_sel();
	spi_outb(SPIFLASH_CMD_READ);
	spi_outb(addr>>16);
	spi_outb(addr>>8);
	spi_outb(addr);

	while (len) {
		unsigned char byte = spi_inb();
		if (byte != *data) {
			printk("\nverify: @%04lx, expect %02x, read %02x\n",
				addr, *data, byte);
			err ++;
		}
		data ++;
		len --;
		addr ++;
	}
	spi_unsel();
	printk("\nverify: error %d\n", err);

	return err;
}


void eth_spi_read_byte(u32 phys, int addr, unsigned char *value)
{
	unsigned int temp = 0;
	printk("%s: Enter!\n", __func__);
	
	addr = 0xffff14;
	virt = (void *)phys;

	virt_out = virt + 0x1;
	virt_en  = virt + 0x2;
	virt_in  = virt + 0x0;
	
	printk("virt_out: %x, virt_en: %x, virt_in: %x\n", *virt_out, *virt_en, *virt_in);
	printk("BAR1 Version :%x\n", *(virt + 0xF));
	virt = virt_out;

	/* spi_enable */
	spi_enable();
	/* spi_reset */
	spi_reset();
		
	spi_readID();

	spi_read_byte(addr, &temp);

	*value = temp;
	/* spi_disable */
	spi_disable();
}
